﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq;
using System.IO;
using System.Threading.Tasks;

namespace Crap4Net.CCParsers
{
    public class VSMetricsParser : ICCParser
    {
        private const string VSMETRIC_METHOD_ELEMENT_NAME = "Member";
        private const string VSMETRIC_ASSEMBLY_ELEMENT_NAME = "Module";
        private const string VSMETRIC_NAMESPACE_ELEMENT_NAME = "Namespace";
        private const string VSMETRIC_TYPE_ELEMENT_NAME = "Type";
        private const string VSMETRIC_METRIC_ELEMENT_NAME = "Metric";
        private const string VSMETRIC_CCMETRIC_NAME = "CyclomaticComplexity";
        
        

        private IList<CCDataEntry> _data;

        IDictionary<string, List<string>> _knownMethods = new Dictionary<string, List<String>>();

        #region ICCParser Members
        public IList<CCDataEntry> Data
        {
            get
            {
                if (_data == null)
                    throw new ApplicationException("Data was not loaded");
                return _data;
            }
            private set
            {
                _data = value;
            }
        }

        public void LoadData(string reportFileName)
        {
            if (!File.Exists(reportFileName))
            {
                throw new ArgumentException("VS Metric report file was not found: " + reportFileName);
            }
            Data = new List<CCDataEntry>();
            ParseDataFromXml(reportFileName);
        }
        #endregion


        private void ParseDataFromXml(string reportFileName)
        {
            IEnumerable<XElement> results = LoadMethodsDataFromFile(reportFileName);
            foreach (var method in results)
            {
                var signature = ParseMethodSignature(method);
                int coverage = GetCCValue(method);

                var newEntry = new CCDataEntry(signature, coverage);
                Data.Add(newEntry);
            }
        }

        private IEnumerable<XElement> LoadMethodsDataFromFile(string reportFileName)
        {
            XDocument loaded = XDocument.Load(reportFileName);
            var result = (from metric in loaded.Descendants()
                          where metric.Name.LocalName == VSMETRIC_METHOD_ELEMENT_NAME
                          select metric);
            return result;
        }

        private MethodSignature ParseMethodSignature(XElement method)
        {
            string assmeblyName = GetAssembly(method);
            string nameSpace = GetNameSpace(method);
            string typeName = GetTypeName(method);
            string methodName = GetMethodName(method);
            string[] args = GetMethodArgs(method);

            return new MethodSignature(assmeblyName, nameSpace, typeName, methodName, args);
        }

        private string GetAssembly(XElement method)
        {
            var assembly = GetNamedParentElement(method,VSMETRIC_ASSEMBLY_ELEMENT_NAME);
            return assembly.Attribute("Name").Value;
        }

        private string GetNameSpace(XElement method)
        {
            var assembly = GetNamedParentElement(method, VSMETRIC_NAMESPACE_ELEMENT_NAME);
            return assembly.Attribute("Name").Value;

        }

        private string GetTypeName(XElement method)
        {
            var typeElement = GetNamedParentElement(method, VSMETRIC_TYPE_ELEMENT_NAME);
            return typeElement.Attribute("Name").Value;
        }

        private string GetMethodName(XElement method)
        {
            var fullMethodName = method.Attribute("Name").Value;
            var shortName = fullMethodName.Split(new char[] { '(' })[0];
            if (shortName == GetTypeName(method))
                return HandleConstructor(method,shortName);
            if (IsProperty(shortName))
                return ConvertPropertyName(shortName);
            return shortName;
        }

        private string HandleConstructor(XElement method,string className)
        {
            if (IsDefaultCtor(method) && AlreadyHasDefaultCtor(className))
                return ".cctor";
            return ".ctor";
        }
        private bool IsDefaultCtor(XElement method)
        {
            return (GetMethodArgs(method).Length == 0);
        }
        private bool AlreadyHasDefaultCtor(string className)
        {
            var result = from entry in Data
                         where entry.Method.TypeName == className && entry.Method.MethodName == ".ctor" && entry.Method.Signature.Count()==0
                         select entry;
            return result.Count() != 0;
        }
        private bool IsProperty(string shortName)
        {
            return shortName.Contains('.');
        }

        private string ConvertPropertyName(string shortName)
        {
            var parts = shortName.Split(new char[] { '.' });
            return parts[1] + "_" + parts[0];
        }

        private string[] GetMethodArgs(XElement method)
        {
            var fullMethodName = method.Attribute("Name").Value;
            int start = fullMethodName.IndexOf('(');
            int end = fullMethodName.IndexOf(')');
            var args = fullMethodName.Substring(start+1, end - start-1);
            if (string.IsNullOrEmpty(args))
                return new string[]{};
            return args.Split(new char[] { ',' });
        }

        private XElement GetNamedParentElement(XElement method, string parentName)
        {
            var parent = method.Parent;
            while (parent.Name.LocalName != parentName)
            {
                parent = parent.Parent;
            }
            return parent;

            throw new NotImplementedException();
        }

        private int GetCCValue(XElement method)
        {
            var t = Task<string>.Factory.StartNew(() =>
            {
                return (from metric in method.Descendants()
                        where IsMetricElement(metric) && MetricIsCC(metric)
                        select metric.Attribute("Value").Value).Single();
            });
            return int.Parse(t.Result);
        }

        private bool IsMetricElement(XElement metric)
        {
            return metric.Name.LocalName == VSMETRIC_METRIC_ELEMENT_NAME;
        }

        private bool MetricIsCC(XElement metric)
        {
            return metric.Attribute("Name").Value == VSMETRIC_CCMETRIC_NAME;
        }
    }
}
